package robocup.component.util;

/**
 * Last-in-First-out-List. This is an endless List of fixed length ;-). When the
 * index reaches the end of the array it will return to the beginning. <p/>
 * Example: add(a),add(b),getLast()->b add(a),add(b),getLast(1)->a
 * add(a),add(b),getLast(2)->null getLast(>=length)->null getLast( <0)->null
 * 
 */
public class RingList<T> {

  protected int index     = -1;

  /** the number of contained items */
  protected int itemCount = 0;

  /** the "list" */
  private final T[]   a;

  /**
   * Constructor a list which is linked to a ring
   * 
   * @param length -
   *          number of items the list contains.
   */
  @SuppressWarnings("unchecked")
  public RingList(
      final int length) {

    // create array of Type T
    this.a = (T[]) java.lang.reflect.Array.newInstance(Object.class, length);
  };

  /**
   * Removes all items of the list
   */
  public void clear() {

    for (int i = 0; i < this.a.length; i++) {
      this.a[i] = null;
    }
    this.itemCount = 0;
  }

  /**
   * The list length.
   * 
   * @return int - length of list
   */
  public int lengthMax() {

    return this.a.length;
  }

  /**
   * The number of items inserted.
   * 
   * @return the number of items inserted (= length of list when list filled)
   */
  public int length() {

    return this.itemCount;
  }

  /**
   * Gets the item inserted on the specified position before the last item.
   * throws IndexOutOfBoundsException when list is empty, when called with a
   * neg. value or when the parameter is greater than the list's length. <p/>
   * Examples: <p/> getLast(0) -> getLast() -> last inserted item getLast(1) ->
   * the item inserted before the last item etc.
   * 
   * @param beforeLast
   * @return the item inserted on the specified position before the last item.
   */
  public T getLast(int beforeLast) {

    if (this.index == -1 || beforeLast > this.a.length - 1 || beforeLast < 0) {
      throw new IndexOutOfBoundsException();
    }
    beforeLast = this.index - beforeLast;
    beforeLast = (beforeLast < 0) ? beforeLast + this.a.length : beforeLast;
    return (T) this.a[beforeLast];
  }

  /**
   * Gets the last inserted item. throws IndexOutOfBoundsException when list is
   * empty.
   * 
   * @return last inserted item
   */
  public T getLast() {

    return (T) this.getLast(0);
  }

  /**
   * Inserts an item. If the list was full before, the oldest item is removed.
   * 
   * @param val -
   */
  public void insert(final T val) {

    this.index++;
    if (this.index >= this.a.length) {
      this.index = 0;
    }
    this.a[this.index] = val;
    if (this.itemCount < this.a.length) {
      this.itemCount++;
    }

  }

  /**
   * Tells if the list is empty
   * 
   * @return true when empty
   */
  public boolean isEmpty() {

    return (this.itemCount == 0);
  }

  /**
   * gets the minimum value of a RingList<Long>
   * 
   * @param maxItems
   *          only the last 'maxItems' are considered
   * @return
   */
  public Long getMin(final int maxItems) {

    if (this.length() == 0) {
      return null;
    }

    if (!(this.getLast() instanceof Long)) {
      return null;
    }

    long min = Long.MAX_VALUE;
    for (int i = 0; i < Math.min(this.length(), maxItems); i++) {
      if ((Long) this.getLast(i) < min) {
        min = (Long) this.getLast(i);
      }
    }
    return min;
  }

  /**
   * gets the minimum value of a RingList<Long>
   * 
   * @param maxItems
   *          only the last 'maxItems' are considered
   * @return
   */
  public Long getMax(final int maxItems) {

    if (this.length() == 0) {
      return null;
    }

    if (!(this.getLast() instanceof Long)) {
      return null;
    }

    long max = Long.MIN_VALUE;
    for (int i = 0; i < Math.min(this.length(), maxItems); i++) {
      if ((Long) this.getLast(i) > max) {
        max = (Long) this.getLast(i);
      }
    }
    return max;
  }
  
  /**
   * gets the minimum value of a RingList<Long>
   * 
   * @param maxItems
   *          only the last 'maxItems' are considered
   * @return
   */
  public double getAVG() {

    if (this.length() == 0) {
      return Double.NaN;
    }

    if (!(this.getLast() instanceof Long)) {
      return Double.NaN;
    }

    long sum = 0;
    
    for (int i = 0; i < this.length(); i++) {
      sum += (Long) this.getLast(i);
    }
    
    return (double) sum / this.length();
  }
}
